﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.IO.Ports;
using System.ComponentModel.Design;
using System.Net.Mail;
using System.Threading;
using System.Collections;
using zj.Communicationlib.ByteArrays;
using System.Runtime.CompilerServices;

namespace zj.Communicationlib.Modbus
{
    /// <summary>
    /// ModbusRTU 库
    /// </summary>
    public class ModbusRTU
    {

        #region  属性
        private SerialPort serialPort;

        /// <summary>
        /// 读入超时时间
        /// </summary>
        public int ReadTimeOut { get; set; } = 2000;
        /// <summary>
        /// 写入超时时间
        /// </summary>
        public int WriteTimeOut { get; set; } = 2000;
        /// <summary>
        /// 接收回复超时
        /// </summary>
        public int ReceiveTimeOut { get; set; } = 5000;
        public int SleepTime { get; set; } = 10;

        private bool isOpen;
        public bool IsOpen
        {
            get
            {
                isOpen = serialPort.IsOpen;
                return isOpen;
            }
            set
            {
                isOpen = value;
            }

        }


        /// <summary>
        /// DTR使能标志
        /// </summary>
        public bool DTREnable
        {
            get
            {

                return serialPort.DtrEnable;
            }
            set
            {
                serialPort.DtrEnable = value;
            }
        }
        /// <summary>
        /// RTS使能标志
        /// </summary>
        public bool RTS_Enable
        {
            get
            {
                return serialPort.RtsEnable;
            }
            set
            {
                serialPort.RtsEnable = value;
            }
        }

        #endregion
        #region 构造函数
        public ModbusRTU()
        {
            serialPort = new SerialPort();
        }
        #endregion
        #region 方法
        #region 通信连接相关
        /// <summary>
        /// 获取机器上的串行口列表
        /// </summary>
        /// <returns></returns>
        public List<string> GetPortNames()
        {
            List<string> portNames = SerialPort.GetPortNames().ToList<string>();
            if (portNames.Count > 0)
            {
                return portNames;
            }
            return null;
        }
        /// <summary>
        /// 建立ModbusRTU串口连接
        /// </summary>
        /// <param name="portName"></param>
        /// <param name="boundrate"></param>
        /// <param name="parity"></param>
        /// <param name="databits"></param>
        /// <param name="stopBits"></param>
        /// <returns></returns>
        public bool Connect(string portName, Boundrate boundrate = Boundrate.BR_9600, Parity parity = Parity.None, Databits databits = Databits.bit_8, StopBits stopBits = StopBits.One)
        {
            serialPort.PortName = portName;
            serialPort.BaudRate = EnumGetNumber<Boundrate>(boundrate);
            serialPort.Parity = parity;
            serialPort.DataBits = EnumGetNumber<Databits>(databits);
            serialPort.StopBits = stopBits;
            serialPort.ReadTimeout = this.ReadTimeOut;
            serialPort.WriteTimeout = this.WriteTimeOut;
            try
            {
                if (serialPort.IsOpen && serialPort != null)
                {
                    serialPort.Close();
                }
                serialPort.Open();
                return true;
            }
            catch (Exception)
            {
                return false;

            }


        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            if (serialPort != null && serialPort.IsOpen)
            {
                serialPort.Close();
                serialPort.Dispose();
            }
        }
        #endregion

        #region 通用的方法




        /// <summary>
        /// 提取枚举对象中的整数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        private int EnumGetNumber<T>(T t) where T : System.Enum
        {
            Type type = typeof(T);
            string str = Enum.GetName(type, t);

            return Convert.ToInt32(str.Substring(str.IndexOf('_') + 1));
        }
        #endregion
        #region Modbus报文收发返回
        /// <summary>
        /// 读取modbus协议 01，02功能码
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="saveType">输入和输出选择</param>
        /// <param name="startAddr"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException">参数写入错误</exception>
        public bool[] ReadMultiCoil(byte slaveId, RegisterSaveType saveType, ushort startAddr, ushort length)
        {
            byte functionCode = 0x00;
            if ((int)saveType > 2 || (int)saveType < 1)
            {
                throw new ArgumentException("ModbusRTU功能输入错误！");
            }
            if (saveType == RegisterSaveType.Input)
            {
                functionCode = 0x02;    //读取输入
            }
            else if (saveType == RegisterSaveType.Hold)
            {
                functionCode = 0x01;     //读取输出
            }
            ByteArray send = new ByteArray();
            send.Add(slaveId);
            send.Add(functionCode);
            send.Add(startAddr);
            send.Add(length);
            byte[] crc = CRC.CRC16(send.Array, send.Length);
            send.AddRange(crc);
            ByteArray receive = new ByteArray();
            int resultLength;
            try
            {
                //结果的字节长度
                if (SendAndReceive(send, ref receive))
                {

                    resultLength = length % 8 == 0 ? length / 8 : length / 8 + 1;
                    //验证报文长度和CRC校验
                    if (receive.Length == 5 + resultLength && CRC.CheckCRC(receive.Array))
                    {
                        //成功则验证地址的类型
                        if (receive[0] == slaveId && receive[2] == resultLength && receive[1] == functionCode)
                        {
                            /******************解析报文**********************/
                            byte[] result = new byte[resultLength];
                            Array.Copy(receive.Array, 3, result, 0, resultLength);  //字节数组复制
                            //把字节数组数据转换为布尔数组数据
                            bool[] bolresult = new bool[length];
                            bolresult = ByteArrayHelper.ByteArrayToBoolArray(result, length);
                            return bolresult;
                        }
                    }
                }
                return null;
            }
            catch (Exception ex)
            {
                throw new Exception($"modbusRTU读取{functionCode}指令错误，请检查。 原因：" + ex.Message);
            }
        }
        /// <summary>
        /// 读取modbus协议 03，04功能码
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="saveType">输入和输出选择</param>
        /// <param name="startAddr"></param>
        /// <param name="length">读取的寄存器数量（每个寄存器2个字节，16位）</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="Exception"></exception>
        public byte[] ReadMultiRegister(byte slaveId, RegisterSaveType saveType, ushort startAddr, ushort length)
        {
            byte functionCode;
            if ((int)saveType > 2 || (int)saveType < 1)
            {
                throw new ArgumentException("ModbusRTU功能输入错误！");
            }
            if (saveType == RegisterSaveType.Input)
            {
                functionCode = 0x04;
            }
            else
            {
                functionCode = 0x03;
            }
            ByteArray send = new ByteArray();
            send.Add(slaveId);
            send.Add(functionCode);
            send.Add(startAddr);
            send.Add(length);
            byte[] crc = CRC.CRC16(send.Array, send.Length);
            send.AddRange(crc);
            ByteArray receive = new ByteArray();
            int resultLength;
            try
            {
                //结果的字节长度
                if (SendAndReceive(send, ref receive))
                {

                    resultLength = length * 2;
                    //验证报文长度和CRC校验
                    if (receive.Length == 5 + resultLength && CRC.CheckCRC(receive.Array))
                    {
                        //成功则验证地址的类型
                        if (receive[0] == slaveId && receive[2] == resultLength && receive[1] == functionCode)
                        {
                            /******************解析报文**********************/
                            byte[] result = new byte[resultLength];
                            Array.Copy(receive.Array, 3, result, 0, resultLength);  //字节数组复制
                            //把字节数组数据返回
                            return result;
                        }
                    }
                }
                return null;
            }
            catch (Exception ex)
            {
                throw new Exception($"modbusRTU读取{functionCode}指令错误，请检查。 原因：" + ex.Message);
            }
        }
        /// <summary>
        /// 读取寄存器的值
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="saveType"></param>
        /// <param name="startAddr"></param>
        /// <param name="length"></param>
        /// <param name="result"></param>
        //public void ReadMultiRegister(byte slaveId, RegisterSaveType saveType, ushort startAddr, ushort length, out ushort[] result)
        //{

        //    ushort[] ushorts = null;
        //    byte[] bytes = ReadMultiRegister(slaveId, saveType, startAddr, length);


        //}


        /// <summary>
        /// 写入单个线圈
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="startAddr"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public bool WriteSingleCoil(byte slaveId, ushort startAddr, bool value)
        {
            //从站地址（一个字节）； 05功能码（一个字节）；线圈地址(两个字节)； 值（两个字节）; CRC(两个字节)
            //拼接报文
            ByteArray send = new ByteArray();
            send.Add(slaveId);
            send.Add(0x05);
            send.Add(startAddr);
            send.Add((byte)(value ? 0xFF : 0x00));
            send.Add(0x00);
            byte[] crc = CRC.CRC16(send.Array, send.Length);
            send.AddRange(crc);
            //发送和接收报文
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send, ref receive);
                //验证报文
                if (ByteArrayHelper.ByteArrayCompare(send, receive))
                {
                    return true;
                }
                else
                {
                    return false;
                }

            }
            catch (Exception ex)
            {
                throw new Exception("modbusRTU写入05功能码错误，请检查。 原因：" + ex.Message);
            }
        }
        /// <summary>
        /// 设置单个寄存器
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="startAddr"></param>
        /// <param name="value">低地址在前，高地址在后</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public bool WriteSingleRegister(byte slaveId, ushort startAddr, byte[] value)
        {
            if (value == null || value.Length != 2)
            {
                return false;
            }
            ByteArray send = new ByteArray();
            send.Add(slaveId);
            send.Add(0x06);
            send.Add(startAddr);
            send.AddRange(value);
            byte[] crc = CRC.CRC16(send.Array, send.Length);
            send.AddRange(crc);
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send, ref receive);
                if (ByteArrayHelper.ByteArrayCompare(send, receive))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("modbusRTU写入06功能码错误，请检查。 原因：" + ex.Message);
            }
        }
        /// <summary>
        /// 写入单个寄存器
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="startAddr"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool WriteSingleRegister(byte slaveId, ushort startAddr, short value, bool byteReverse)
        {
            if (!byteReverse)
            {
                return WriteSingleRegister(slaveId, startAddr, BitConverter.GetBytes(value).Reverse().ToArray());
            }
            else
            {
                return WriteSingleRegister(slaveId, startAddr, BitConverter.GetBytes(value));
            }

        }
        /// <summary>
        /// 写入单个寄存器
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="startAddr"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool WriteSingleRegister(byte slaveId, ushort startAddr, ushort value, bool byteReverse)
        {
            if (!byteReverse)
            {
                return WriteSingleRegister(slaveId, startAddr, BitConverter.GetBytes(value).Reverse().ToArray());
            }
            else
            {
                return WriteSingleRegister(slaveId, startAddr, BitConverter.GetBytes(value));
            }

        }
        /// <summary>
        /// 写入多个线圈
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="startAddr"></param>
        /// <param name="value">数组的低字节对应MODBUS的低字节</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public bool WriteMultiCoils(byte slaveId, ushort startAddr, bool[] value)
        {
            if (value == null || value.Length == 0)
            {
                return false;
            }
            //从站地址（一个字节）； 0F功能码（一个字节）；起始线圈地址；线圈数量; 数据的字节计数；写入的数据；CRC(两个字节)
            ByteArray send = new ByteArray();
            send.Add(slaveId);
            send.Add(0x0F);
            send.Add(startAddr);
            send.Add((short)value.Length);  //线圈数量
            send.Add((byte)(value.Length % 8 == 0 ? value.Length / 8 : value.Length / 8 + 1)); //字节计数
            send.AddRange(value);
            byte[] crc = CRC.CRC16(send.Array, send.Length);
            send.AddRange(crc);
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send, ref receive);
                if (CRC.CheckCRC(receive.Array))
                {
                    if (ByteArrayHelper.ByteArrayCompare(send, receive, 6))
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("modbusRTU写入0F功能码错误，请检查。 原因：" + ex.Message);
            }

        }
        /// <summary>
        /// 写入多输出寄存器
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="startAddr"></param>
        /// <param name="value">低地址在前，高地址在后</param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort startAddr, byte[] value)
        {
            if (value == null || value.Length == 0 || value.Length % 2 == 1)
            {
                return false;
            }
            //从站地址（一个字节）； 10功能码（一个字节）；起始寄存器地址(两个字节)；寄存器数量（两个字节）; 字节数量（一个字节）；写入的数据（）；CRC(两个字节)
            ByteArray send = new ByteArray();
            send.Add(slaveId);
            send.Add(0x10);
            send.Add(startAddr);
            send.Add((short)(value.Length / 2));
            send.Add((byte)(value.Length));
            send.AddRange(value);
            byte[] crc = CRC.CRC16(send.Array, send.Length);
            send.AddRange(crc);
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send, ref receive);
                if (CRC.CheckCRC(receive.Array))
                {
                    if (ByteArrayHelper.ByteArrayCompare(send, receive, 6))
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("modbusRTU写入10功能码错误，请检查。 原因：" + ex.Message);
            }


        }
        /// <summary>
        /// 写入多输出寄存器
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <param name="byteReverse">是否字节交换</param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, ushort[] value, bool byteReverse)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;
            foreach (ushort i in value)
            {
                if (!byteReverse)
                {
                    bytes = BitConverter.GetBytes(i).Reverse().ToArray();
                }
                else
                {
                    bytes = BitConverter.GetBytes(i);
                }

                byteArray.AddRange(bytes);
            }
            return WriteMultiRegister(slaveId, start, byteArray.Array);
        }
        /// <summary>
        ///  写入多个有符号16位数
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, short[] value, bool byteReverse)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;
            foreach (short i in value)
            {
                if (!byteReverse)
                {
                    bytes = BitConverter.GetBytes(i).Reverse().ToArray();
                }
                else
                {
                    bytes = BitConverter.GetBytes(i);
                }

                byteArray.AddRange(bytes);
            }
            return WriteMultiRegister(slaveId, start, byteArray.Array);
        }

        /// <summary>
        /// 写入多个无符号32位数
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, uint[] value, RegisterFormat registerFormat = RegisterFormat.ABCD)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;
            foreach (uint i in value)
            {
                bytes = i.GetByteArray(s => BitConverter.GetBytes(s).Reverse().ToArray(), registerFormat);
                byteArray.AddRange(bytes);
            }
            return WriteMultiRegister(slaveId, start, byteArray.Array);

        }
        /// <summary>
        /// 写入多个有符号32位数
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <param name="registerFormat"></param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, int[] value, RegisterFormat registerFormat = RegisterFormat.ABCD)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;
            foreach (int i in value)
            {
                bytes = i.GetByteArray(s => BitConverter.GetBytes(s).Reverse().ToArray(), registerFormat);
                byteArray.AddRange(bytes);
            }
            return WriteMultiRegister(slaveId, start, byteArray.Array);

        }
        /// <summary>
        /// 写入多个有符号的64位长整数
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <param name="registerFormat"></param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, long[] value, RegisterFormat registerFormat = RegisterFormat.ABCD)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;
            foreach (long i in value)
            {
                bytes = i.GetByteArray(s => BitConverter.GetBytes(s).Reverse().ToArray(), registerFormat);
                byteArray.AddRange(bytes);
            }

            return WriteMultiRegister(slaveId, start, byteArray.Array);

        }
        /// <summary>
        /// 写入多个无符号的64位长整数
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <param name="registerFormat"></param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, ulong[] value, RegisterFormat registerFormat = RegisterFormat.ABCD)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;

            foreach (ulong i in value)
            {

                bytes = i.GetByteArray(s => BitConverter.GetBytes(s).Reverse().ToArray(), registerFormat);
                byteArray.AddRange(bytes);
            }

            return WriteMultiRegister(slaveId, start, byteArray.Array);
        }
        /// <summary>
        /// 写入多个单精度浮点数
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <param name="registerFormat"></param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, float[] value, RegisterFormat registerFormat = RegisterFormat.ABCD)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;

            foreach (float i in value)
            {

                bytes = i.GetByteArray(s => BitConverter.GetBytes(s).Reverse().ToArray(), registerFormat);
                byteArray.AddRange(bytes);
            }

            return WriteMultiRegister(slaveId, start, byteArray.Array);
        }

        /// <summary>
        /// 写入多个双精度浮点数
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <param name="registerFormat"></param>
        /// <returns></returns>
        public bool WriteMultiRegister(byte slaveId, ushort start, double[] value, RegisterFormat registerFormat = RegisterFormat.ABCD)
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;

            foreach (double i in value)
            {

                bytes = i.GetByteArray(s => BitConverter.GetBytes(s).Reverse().ToArray(), registerFormat);
                byteArray.AddRange(bytes);
            }

            return WriteMultiRegister(slaveId, start, byteArray.Array);
        }
        /// <summary>
        /// 利用泛型实现通用的写入多寄存器的方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="slaveId"></param>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <param name="func"></param>
        /// <param name="registerFormat"></param>
        /// <returns></returns>
        public bool WriteMultiRegister<T>(byte slaveId, ushort start, T[] value, Func<T, byte[]> func, RegisterFormat registerFormat = RegisterFormat.ABCD)
        where T : struct
        {
            ByteArray byteArray = new ByteArray();
            byte[] bytes = null;
            foreach (T i in value)
            {

                bytes = ByteArrayHelper.GetByteArray(i, func, registerFormat);
                byteArray.AddRange(bytes);
            }

            return WriteMultiRegister(slaveId, start, byteArray.Array);
        }



        #endregion


        /// <summary>
        /// 发送和接收数据
        /// </summary>
        /// <param name="send">发送字节数组</param>
        /// <param name="receive">接收的字节数组</param>
        /// <returns>返回结果: 成功：true 失败：false</returns>
        private bool SendAndReceive(ByteArray send, ref ByteArray receive)
        {


            try
            {
                Monitor.Enter(this);
                serialPort.Write(send.Array, 0, send.Length);     //发送数据
                byte[] receiveBuff = new byte[1024];
                DateTime StartReceiveTime = DateTime.Now;
                while (true)
                {
                    Thread.Sleep(SleepTime);
                    if (serialPort.BytesToRead > 0)
                    {
                        int count = serialPort.Read(receiveBuff, 0, receiveBuff.Length);
                        receive.AddRange(receiveBuff, count);
                    }
                    else
                    {
                        if (receive.Length > 0)
                        {

                            return true;
                        }
                        else if (DateTime.Now.Subtract(StartReceiveTime).TotalMilliseconds > ReceiveTimeOut)
                        {
                            return false;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception("串口读写出错！原因：" + ex.Message);
            }
            finally
            {
                Monitor.Exit(this);
            }


        }


        #endregion
    }
}
